home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1998 April: Mac OS SDK / Dev.CD Apr 98 SDK1.toast / Development Kits (Disc 1) / QuickDraw 3D / Windows files / Q3WinSDK.exe / QD3DSDK / Samples / ViewerSample / Box3DSupport.c < prev    next >
Encoding:
C/C++ Source or Header  |  1996-03-19  |  16.0 KB  |  580 lines

  1. // Box3dSupport.c - QuickDraw 3d routines
  2. //
  3. // This file contains utility routines for QuickDraw 3d sample code.
  4. // This is a simple QuickDraw 3d application to draw a cube in the center 
  5. // of the main application window.  The routines in here handle setting up
  6. // the main display group, the view, the Macintosh 3D draw context, and the
  7. // camera and lighting. 
  8. //
  9. // This code is the basis of the introductory article in  d e v e l o p  issue 22
  10. //
  11. // Nick Thompson - January 6th 1995
  12. // ©1994-95 Apple computer Inc., All Rights Reserved
  13. // 
  14. //
  15. #include "Box3DSupport.h"
  16.  
  17. #define ErMath_Atan(x)    ((float)atan((double)(x)))
  18. #define    kEPSILON        1.19209290e-07
  19.  
  20.  
  21. static     TQ3Point3D    documentGroupCenter;
  22. static    float        documentGroupScale;
  23.  
  24.  
  25. TQ3ViewObject MyNewView(DocumentPtr theDocument)
  26. {
  27.     TQ3Status                myStatus;
  28.     TQ3ViewObject            myView;
  29.     TQ3DrawContextObject    myDrawContext;
  30.     TQ3RendererObject        myRenderer;
  31.     TQ3CameraObject            myCamera;
  32.     TQ3GroupObject            myLights;
  33.     
  34.     myView = Q3View_New();
  35.     
  36.     //    Create and set draw context.
  37.     if ((myDrawContext = MyNewDrawContext(theDocument)) == NULL )
  38.         goto bail;
  39.         
  40.     if ((myStatus = Q3View_SetDrawContext(myView, myDrawContext)) == kQ3Failure )
  41.         goto bail;
  42.  
  43.     Q3Object_Dispose( myDrawContext ) ;
  44.     
  45.     //    Create and set renderer.
  46.     
  47.     // this uses the wire frame renderer
  48. #if 0
  49.  
  50.     myRenderer = Q3Renderer_NewFromType(kQ3RendererTypeWireFrame);
  51.     if ((myStatus = Q3View_SetRenderer(myView, myRenderer)) == kQ3Failure ) {
  52.         goto bail;
  53.     }
  54.     
  55. #else
  56.  
  57.     // this uses the interactive software renderer
  58.  
  59.     if ((myRenderer = Q3Renderer_NewFromType(kQ3RendererTypeInteractive)) != NULL ) {
  60.         if ((myStatus = Q3View_SetRenderer(myView, myRenderer)) == kQ3Failure ) {
  61.             goto bail;
  62.         }
  63.         // these two lines set us up to use the best possible renderer,
  64.         // including  hardware if it is installed.
  65.         Q3InteractiveRenderer_SetDoubleBufferBypass (myRenderer, kQ3True);                        
  66.         Q3InteractiveRenderer_SetPreferences(myRenderer, kQAVendor_BestChoice, 0);
  67.  
  68.     }
  69.     else {
  70.         goto bail;
  71.     }
  72. #endif
  73.  
  74.     Q3Object_Dispose( myRenderer ) ;
  75.     
  76.     //    Create and set camera
  77.     if ( (myCamera = MyNewCamera(theDocument)) == NULL )
  78.         goto bail;
  79.         
  80.     if ((myStatus = Q3View_SetCamera(myView, myCamera)) == kQ3Failure )
  81.         goto bail;
  82.  
  83.     Q3Object_Dispose( myCamera ) ;
  84.     
  85.     //    Create and set lights
  86.     if ((myLights = MyNewLights()) == NULL )
  87.         goto bail;
  88.         
  89.     if ((myStatus = Q3View_SetLightGroup(myView, myLights)) == kQ3Failure )
  90.         goto bail;
  91.         
  92.     Q3Object_Dispose(myLights);
  93.  
  94.     return ( myView );
  95.     
  96. bail:
  97.     //    If any of the above failed, then don't return a view.
  98.     return ( NULL );
  99. }
  100.  
  101. //----------------------------------------------------------------------------------
  102.  
  103. TQ3DrawContextObject MyNewDrawContext(DocumentPtr theDocument)
  104. {
  105.     TQ3DrawContextData            myDrawContextData;
  106.     TQ3PixmapDrawContextData    myPixmapDrawContextData;
  107.     TQ3ColorARGB                ClearColor;
  108.     TQ3DrawContextObject        myDrawContext;
  109.     TQ3Pixmap                    aPixmap;
  110.      BITMAPINFO                    bitmapInfo;
  111.     HDC                            hdc;
  112.     TQ3Boolean                    is32Bit = kQ3False;
  113.  
  114.         //    Set the background color.
  115.     ClearColor.a = 0.0;
  116.     ClearColor.r = 1.0;
  117.     ClearColor.g = 1.0;
  118.     ClearColor.b = 1.0;
  119.  
  120.         //    fill in draw context data.
  121.     myDrawContextData.clearImageMethod = kQ3ClearMethodWithColor;
  122.     myDrawContextData.clearImageColor = ClearColor;
  123.     myDrawContextData.paneState = kQ3False;
  124.     myDrawContextData.maskState = kQ3False;
  125.     myDrawContextData.doubleBufferState = kQ3False;
  126.  
  127.         // set up device independent bitmap
  128.     hdc = GetDC(theDocument->fWindow);
  129.     theDocument->fMemoryDC = CreateCompatibleDC(hdc);
  130.  
  131.     bitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);    
  132.     bitmapInfo.bmiHeader.biWidth = theDocument->fWidth;    
  133.     bitmapInfo.bmiHeader.biHeight = -theDocument->fHeight;    
  134.  
  135.     bitmapInfo.bmiHeader.biPlanes = 1;
  136.     if (is32Bit == kQ3True)
  137.         bitmapInfo.bmiHeader.biBitCount = 32;
  138.     else
  139.         bitmapInfo.bmiHeader.biBitCount = 16;    
  140.     bitmapInfo.bmiHeader.biCompression = BI_RGB;    
  141.     bitmapInfo.bmiHeader.biSizeImage = 0;    
  142.     bitmapInfo.bmiHeader.biXPelsPerMeter = 0;    
  143.     bitmapInfo.bmiHeader.biYPelsPerMeter = 0;    
  144.     bitmapInfo.bmiHeader.biClrUsed = 0;    
  145.     bitmapInfo.bmiHeader.biClrImportant = 0;    
  146.  
  147.     theDocument->fBitmap = CreateDIBSection(hdc, &bitmapInfo, DIB_RGB_COLORS,    
  148.                                             &theDocument->fBitStorage, NULL, 0);    
  149.     SelectObject(theDocument->fMemoryDC, theDocument->fBitmap);
  150.  
  151.         /* create a pixmap */
  152.     aPixmap.width = theDocument->fWidth;
  153.     aPixmap.height = theDocument->fHeight;
  154.     aPixmap.image = theDocument->fBitStorage;
  155.     if (is32Bit == kQ3True)
  156.     {
  157.         aPixmap.rowBytes = aPixmap.width * 4;
  158.         aPixmap.pixelSize = 32;
  159.         aPixmap.pixelType = kQ3PixelTypeRGB32;
  160.     }
  161.     else
  162.     {
  163.         aPixmap.rowBytes = (aPixmap.width * (bitmapInfo.bmiHeader.biBitCount/8));
  164.         aPixmap.rowBytes = (aPixmap.rowBytes + 3) & ~3L; // make it long aligned
  165.         aPixmap.pixelSize = 16;
  166.         aPixmap.pixelType = kQ3PixelTypeRGB16;
  167.     }
  168.     aPixmap.bitOrder = kQ3EndianBig;
  169.     aPixmap.byteOrder = kQ3EndianBig; /* this should be Little, but that crashes */
  170.  
  171.         /* set up the pixmapDrawContext */
  172.     myPixmapDrawContextData.pixmap = aPixmap;
  173.     myPixmapDrawContextData.drawContextData = myDrawContextData;
  174.  
  175.     //    Create draw context and return it, if it's nil the caller must handle
  176.     myDrawContext = Q3PixmapDrawContext_New(&myPixmapDrawContextData) ;
  177.  
  178.     return myDrawContext ;
  179. }
  180.  
  181. //----------------------------------------------------------------------------------
  182.  
  183. TQ3CameraObject MyNewCamera(DocumentPtr theDocument)
  184. {
  185.     TQ3ViewAngleAspectCameraData    perspectiveData;
  186.  
  187.     TQ3CameraObject                camera;
  188.     
  189.     TQ3Point3D                     from     = { 0.0, 0.0, 7.0 };
  190.     TQ3Point3D                     to         = { 0.0, 0.0, 0.0 };
  191.     TQ3Vector3D                 up         = { 0.0, 1.0, 0.0 };
  192.  
  193.     float                         fieldOfView = 1.0;
  194.     float                         hither         = 0.001;
  195.     float                         yon         = 1000;
  196.  
  197.     TQ3Status                    returnVal = kQ3Failure ;
  198.  
  199.     perspectiveData.cameraData.placement.cameraLocation     = from;
  200.     perspectiveData.cameraData.placement.pointOfInterest     = to;
  201.     perspectiveData.cameraData.placement.upVector             = up;
  202.  
  203.     perspectiveData.cameraData.range.hither    = hither;
  204.     perspectiveData.cameraData.range.yon     = yon;
  205.  
  206.     perspectiveData.cameraData.viewPort.origin.x = -1.0;
  207.     perspectiveData.cameraData.viewPort.origin.y = 1.0;
  208.     perspectiveData.cameraData.viewPort.width = 2.0;
  209.     perspectiveData.cameraData.viewPort.height = 2.0;
  210.  
  211.     perspectiveData.fov                = fieldOfView;
  212.     perspectiveData.aspectRatioXToY    =
  213.         (float) (theDocument->fWidth) / 
  214.         (float) (theDocument->fHeight);
  215.         
  216.     camera = Q3ViewAngleAspectCamera_New(&perspectiveData);
  217.  
  218.     return camera ;
  219. }
  220.  
  221.  
  222. //----------------------------------------------------------------------------------
  223.  
  224. TQ3GroupObject MyNewLights()
  225. {
  226.     TQ3GroupPosition        myGroupPosition;
  227.     TQ3GroupObject            myLightList;
  228.     TQ3LightData            myLightData;
  229.     TQ3PointLightData        myPointLightData;
  230.     TQ3DirectionalLightData    myDirectionalLightData;
  231.     TQ3LightObject            myAmbientLight, myPointLight, myFillLight;
  232.     TQ3Point3D                pointLocation = { -10.0, 0.0, 10.0 };
  233.     TQ3Vector3D                fillDirection = { 10.0, 0.0, 10.0 };
  234.     TQ3ColorRGB                WhiteLight = { 1.0, 1.0, 1.0 };
  235.     
  236.     //    Set up light data for ambient light.  This light data will be used for point and fill
  237.     //    light also.
  238.  
  239.     myLightData.isOn = kQ3True;
  240.     myLightData.color = WhiteLight;
  241.     
  242.     //    Create ambient light.
  243.     myLightData.brightness = .25;
  244.     myAmbientLight = Q3AmbientLight_New(&myLightData);
  245.     if ( myAmbientLight == NULL )
  246.         goto bail;
  247.     
  248.     //    Create point light.
  249.     myLightData.brightness = 1.0;
  250.     myPointLightData.lightData = myLightData;
  251.     myPointLightData.castsShadows = kQ3False;
  252.     myPointLightData.attenuation = kQ3AttenuationTypeNone;
  253.     myPointLightData.location = pointLocation;
  254.     myPointLight = Q3PointLight_New(&myPointLightData);
  255.     if ( myPointLight == NULL )
  256.         goto bail;
  257.  
  258.     //    Create fill light.
  259.     myLightData.brightness = .3;
  260.     myDirectionalLightData.lightData = myLightData;
  261.     myDirectionalLightData.castsShadows = kQ3False;
  262.     myDirectionalLightData.direction = fillDirection;
  263.     myFillLight = Q3DirectionalLight_New(&myDirectionalLightData);
  264.     if ( myFillLight == NULL )
  265.         goto bail;
  266.  
  267.     //    Create light group and add each of the lights into the group.
  268.     myLightList = Q3LightGroup_New();
  269.     if ( myLightList == NULL )
  270.         goto bail;
  271.     myGroupPosition = Q3Group_AddObject(myLightList, myAmbientLight);
  272.     if ( myGroupPosition == 0 )
  273.         goto bail;
  274.     myGroupPosition = Q3Group_AddObject(myLightList, myPointLight);
  275.     if ( myGroupPosition == 0 )
  276.         goto bail;
  277.     myGroupPosition = Q3Group_AddObject(myLightList, myFillLight);
  278.     if ( myGroupPosition == 0 )
  279.         goto bail;
  280.  
  281.     Q3Object_Dispose( myAmbientLight ) ;
  282.     Q3Object_Dispose( myPointLight ) ;
  283.     Q3Object_Dispose( myFillLight ) ;
  284.  
  285.     //    Done!
  286.     return ( myLightList );
  287.     
  288. bail:
  289.     //    If any of the above failed, then return nothing!
  290.     return ( NULL );
  291. }
  292.  
  293.  
  294.  
  295.  
  296. static void MyColorBoxFaces( TQ3BoxData *myBoxData )
  297. {
  298.     TQ3ColorRGB                faceColor ;
  299.     short                     face ;
  300.     
  301.     // sanity check - you need to have set up 
  302.     // the face attribute set for the box data 
  303.     // before calling this.
  304.     
  305.     if( myBoxData->faceAttributeSet == NULL )
  306.         return ;
  307.         
  308.     // make each face of a box a different color
  309.     
  310.     for( face = 0; face < 6; face++) {
  311.         
  312.         myBoxData->faceAttributeSet[face] = Q3AttributeSet_New();
  313.         switch( face ) {
  314.             case 0:
  315.                 faceColor.r = 1.0;
  316.                 faceColor.g = 0.0;
  317.                 faceColor.b = 0.0;
  318.                 break;
  319.             
  320.             case 1:
  321.                 faceColor.r = 0.0;
  322.                 faceColor.g = 1.0;
  323.                 faceColor.b = 0.0;
  324.                 break;
  325.             
  326.             case 2:
  327.                 faceColor.r = 0.0;
  328.                 faceColor.g = 0.0;
  329.                 faceColor.b = 1.0;
  330.                 break;
  331.             
  332.             case 3:
  333.                 faceColor.r = 1.0;
  334.                 faceColor.g = 1.0;
  335.                 faceColor.b = 0.0;
  336.                 break;
  337.             
  338.             case 4:
  339.                 faceColor.r = 1.0;
  340.                 faceColor.g = 0.0;
  341.                 faceColor.b = 1.0;
  342.                 break;
  343.             
  344.             case 5:
  345.                 faceColor.r = 0.0;
  346.                 faceColor.g = 1.0;
  347.                 faceColor.b = 1.0;
  348.                 break;
  349.         }
  350.         Q3AttributeSet_Add(myBoxData->faceAttributeSet[face], kQ3AttributeTypeDiffuseColor, &faceColor);
  351.     }
  352. }
  353.  
  354. static TQ3GroupPosition MyAddTransformedObjectToGroup( TQ3GroupObject theGroup, TQ3Object theObject, TQ3Vector3D *translation )
  355. {
  356.     TQ3TransformObject    transform;
  357.  
  358.     transform = Q3TranslateTransform_New(translation);
  359.     Q3Group_AddObject(theGroup, transform);    
  360.     Q3Object_Dispose(transform);
  361.     return Q3Group_AddObject(theGroup, theObject);    
  362. }
  363.  
  364.  
  365. TQ3GroupObject MyNewModel()
  366. {
  367.     TQ3GroupObject            myGroup = NULL;
  368.     TQ3GeometryObject        myBox;
  369.     TQ3BoxData                myBoxData;
  370.     TQ3GroupPosition        myGroupPosition;
  371.     TQ3ShaderObject            myIlluminationShader ;
  372.     TQ3Vector3D                translation;
  373.     unsigned long            i, j;
  374.     
  375.     TQ3SetObject            faces[6] ;
  376.     short                    face ;
  377.             
  378.     // Create a group for the complete model.
  379.     // do not use Q3OrderedDisplayGroup_New since in this
  380.     // type of group all of the translations are applied before
  381.     // the objects in the group are drawn, in this instance we 
  382.     // dont want this.
  383.     if ((myGroup = Q3DisplayGroup_New()) != NULL ) {
  384.             
  385.         // Define a shading type for the group
  386.         // and add the shader to the group
  387.         myIlluminationShader = Q3PhongIllumination_New();
  388.         Q3Group_AddObject(myGroup, myIlluminationShader);
  389.  
  390.         // set up the colored faces for the box data
  391.         myBoxData.faceAttributeSet = faces;
  392.         myBoxData.boxAttributeSet = NULL;
  393.         MyColorBoxFaces( &myBoxData ) ;
  394.         
  395.         #define    kBoxSide        0.8
  396.         #define    kBoxSidePlusGap    0.1
  397.  
  398.         // create the box itself
  399.         Q3Point3D_Set(&myBoxData.origin, 0, 0, 0);
  400.         Q3Vector3D_Set(&myBoxData.orientation, 0, kBoxSide, 0);
  401.         Q3Vector3D_Set(&myBoxData.majorAxis, 0, 0, kBoxSide);    
  402.         Q3Vector3D_Set(&myBoxData.minorAxis, kBoxSide, 0, 0);    
  403.         myBox = Q3Box_New(&myBoxData);
  404.  
  405.         translation.x =  0;                translation.y = kBoxSidePlusGap; translation.z =  0;
  406.         MyAddTransformedObjectToGroup( myGroup, myBox, &translation ) ;
  407.         translation.x =  2 * kBoxSide;    translation.y = kBoxSidePlusGap; translation.z =  0;
  408.         MyAddTransformedObjectToGroup( myGroup, myBox, &translation ) ;
  409.         translation.x =  0;                translation.y = kBoxSidePlusGap; translation.z = -2 * kBoxSide;
  410.         MyAddTransformedObjectToGroup( myGroup, myBox, &translation ) ;
  411.         translation.x = -2 * kBoxSide;    translation.y = kBoxSidePlusGap; translation.z =  0;
  412.         MyAddTransformedObjectToGroup( myGroup, myBox, &translation ) ;
  413.     }
  414.     
  415.     // dispose of the objects we created here
  416.     if( myIlluminationShader ) 
  417.         Q3Object_Dispose(myIlluminationShader);    
  418.             
  419.     for( face = 0; face < 6; face++) {
  420.         if( myBoxData.faceAttributeSet[face] != NULL )
  421.             Q3Object_Dispose(myBoxData.faceAttributeSet[face]);
  422.     }
  423.     
  424.     if( myBox ) 
  425.         Q3Object_Dispose( myBox );
  426.     
  427.     //    Done!
  428.     return ( myGroup );
  429. }
  430.  
  431. void pvCamera_Fit(DocumentPtr theDocument)
  432. {    
  433.     TQ3Point3D         from, to;
  434.     TQ3BoundingBox    viewBBox;
  435.     float            fieldOfView, hither, yon;
  436.  
  437.     if (!theDocument)
  438.         return;
  439.  
  440.     if (!theDocument->fModel)
  441.         return;
  442.  
  443.     pvBBox_Get(theDocument, &viewBBox);
  444.     pvBBoxCenter(&viewBBox, &to);
  445.     
  446.  
  447.     {
  448.         TQ3Vector3D viewVector;
  449.         TQ3Vector3D    normViewVector;
  450.         TQ3Vector3D    eyeToFrontClip;
  451.         TQ3Vector3D    eyeToBackClip;
  452.          TQ3Vector3D    diagonalVector;
  453.         float        viewDistance;
  454.         float         maxDimension;
  455.  
  456.         Q3Point3D_Subtract(&viewBBox.max,
  457.                            &viewBBox.min,
  458.                            &diagonalVector);
  459.         maxDimension = Q3Vector3D_Length(&diagonalVector);
  460.         if (maxDimension == 0.0)
  461.             maxDimension = 1.0;
  462.  
  463.         maxDimension *= 8.0 / 7.0;
  464.     
  465.         from.x = to.x;
  466.         from.y = to.y;
  467.         from.z = to.z + (2 * maxDimension);        
  468.  
  469.         Q3Point3D_Subtract(&to, &from, &viewVector);
  470.         viewDistance = Q3Vector3D_Length(&viewVector);
  471.         Q3Vector3D_Normalize(&viewVector, &normViewVector);
  472.         
  473.         maxDimension /= 2.0;
  474.         
  475.         Q3Vector3D_Scale(&normViewVector, 
  476.                         viewDistance - maxDimension,
  477.                         &eyeToFrontClip);
  478.                         
  479.         Q3Vector3D_Scale(&normViewVector, 
  480.                         viewDistance + maxDimension,
  481.                         &eyeToBackClip);
  482.         
  483.         hither     = Q3Vector3D_Length(&eyeToFrontClip);
  484.         yon     = Q3Vector3D_Length(&eyeToBackClip);
  485.         
  486.         fieldOfView = Q3Math_RadiansToDegrees(1.25 * ErMath_Atan(maxDimension/hither));
  487.     }
  488.  
  489.     {
  490.         TQ3ViewAngleAspectCameraData     data;
  491.         TQ3Vector3D     up     = { 0.0, 1.0, 0.0 };
  492.  
  493.         data.cameraData.placement.cameraLocation     = from;
  494.         data.cameraData.placement.pointOfInterest     = to;
  495.         data.cameraData.placement.upVector             = up;
  496.     
  497.         data.cameraData.range.hither = hither;
  498.         data.cameraData.range.yon      = yon;
  499.     
  500.         data.cameraData.viewPort.origin.x = -1.0;
  501.         data.cameraData.viewPort.origin.y =  1.0;
  502.         data.cameraData.viewPort.width  = 2.0;
  503.         data.cameraData.viewPort.height = 2.0;
  504.  
  505.         data.fov = Q3Math_DegreesToRadians(fieldOfView);
  506.         
  507.         {
  508.             float w = (float)(theDocument->fWidth);
  509.             float h = (float)(theDocument->fHeight);
  510.  
  511.             data.aspectRatioXToY = w/h;
  512.         }
  513.         
  514.         if (theDocument->fView)
  515.         {
  516.             TQ3CameraObject camera;
  517.             
  518.             Q3View_GetCamera(theDocument->fView, &camera);
  519.             if (camera) {
  520.                 Q3ViewAngleAspectCamera_SetData(camera, &data);
  521.                 Q3Object_Dispose(camera);
  522.             }
  523.             else {
  524.                 camera  = Q3ViewAngleAspectCamera_New (&data);
  525.                 if (camera) {
  526.                     Q3View_SetCamera (theDocument->fView, camera);
  527.                     Q3Object_Dispose(camera);
  528.                 }
  529.             }
  530.         }
  531.     }
  532. }
  533.  
  534. void pvBBox_Get(DocumentPtr theDocument, TQ3BoundingBox *bbox)
  535. {            
  536.     if (theDocument->fView)
  537.     {
  538.         Q3View_StartBoundingBox(theDocument->fView, kQ3ComputeBoundsExact);
  539.         do
  540.         {
  541.             if (Q3DisplayGroup_Submit(theDocument->fModel, theDocument->fView) == kQ3Failure)
  542.             {
  543.                 Q3View_Cancel(theDocument->fView);
  544.                 return;
  545.             }
  546.         } while (Q3View_EndBoundingBox(theDocument->fView, bbox) == kQ3ViewStatusRetraverse);
  547.     }
  548.     else
  549.     {
  550.         Q3Point3D_Set(&(bbox->min), -0.1, -0.1, -0.1);
  551.         Q3Point3D_Set(&(bbox->max), 0.1, 0.1, 0.1);
  552.         bbox->isEmpty = kQ3False;
  553.     }
  554. }
  555.  
  556. void pvBBoxCenter(TQ3BoundingBox *bbox, TQ3Point3D *center)
  557. {
  558.      float    xSize, ySize, zSize;
  559.  
  560.     xSize = bbox->max.x - bbox->min.x;
  561.     ySize = bbox->max.y - bbox->min.y;
  562.     zSize = bbox->max.z - bbox->min.z;
  563.  
  564.     if (xSize <= kEPSILON &&
  565.         ySize <= kEPSILON &&
  566.         zSize <= kEPSILON)  {
  567.         bbox->max.x += 0.0001;
  568.         bbox->max.y += 0.0001;
  569.         bbox->max.z += 0.0001;
  570.         
  571.         bbox->min.x -= 0.0001;
  572.         bbox->min.y -= 0.0001;
  573.         bbox->min.z -= 0.0001;
  574.     }
  575.     
  576.     center->x = (bbox->min.x + bbox->max.x) / 2.0;
  577.     center->y = (bbox->min.y + bbox->max.y) / 2.0;
  578.     center->z = (bbox->min.z + bbox->max.z) / 2.0;
  579. }
  580.